/**
 * Copyright (c) 2008, http://www.snakeyaml.org
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitee.wsl.transform.escape.sample

import com.gitee.wsl.ext.string.appendCodePoint
import com.gitee.wsl.ext.string.codePointAt
import com.gitee.wsl.lang.character.charCount
import com.gitee.wsl.lang.character.forEachCodePoint
import com.gitee.wsl.transform.escape.Escape
import com.gitee.wsl.transform.escape.EscapeString
import com.gitee.wsl.transform.escape.escape


object YamlEscape: EscapeString() {

    override fun escape(scalar: String): String {
        if (scalar.isEmpty()) return scalar

        // Indicators and special characters.
        var blockIndicators = false
        var flowIndicators = false
        var lineBreaks = false
        var specialCharacters = false

        // Important whitespace combinations.
        var leadingSpace = false
        var leadingBreak = false
        var trailingSpace = false
        var trailingBreak = false
        var breakSpace = false
        var spaceBreak = false

        // Check document indicators.
        if (scalar.startsWith("---") || scalar.startsWith("...")) {
            blockIndicators = true
            flowIndicators = true
        }
        // First character or preceded by a whitespace.
        var preceededByWhitespace = true
        var followedByWhitespace = scalar.length == 1 || Constant.NULL_BL_T_LINEBR.has(scalar.codePointAt(1))
        // The previous character is a space.
        var previousSpace = false

        // The previous character is a break.
        var previousBreak = false

        var index = 0

        while (index < scalar.length) {
            scalar.forEachCodePoint {  }
            val c: Int = scalar.codePointAt(index)
            // Check for indicators.
            if (index == 0) {
                // Leading indicators are special characters.
                if ("#,[]{}&*!|>'\"%@`".indexOf(c.toChar()) != -1) {
                    flowIndicators = true
                    blockIndicators = true
                }
                if (c == '?'.code || c == ':'.code) {
                    flowIndicators = true
                    if (followedByWhitespace) {
                        blockIndicators = true
                    }
                }
                if (c == '-'.code && followedByWhitespace) {
                    flowIndicators = true
                    blockIndicators = true
                }
            } else {
                // Some indicators cannot appear within a scalar as well.
                if (",?[]{}".indexOf(c.toChar()) != -1) {
                    flowIndicators = true
                }
                if (c == ':'.code) {
                    flowIndicators = true
                    if (followedByWhitespace) {
                        blockIndicators = true
                    }
                }
                if (c == '#'.code && preceededByWhitespace) {
                    flowIndicators = true
                    blockIndicators = true
                }
            }
            // Check for line breaks, special, and unicode characters.
            val isLineBreak = Constant.LINEBR.has(c)
            if (isLineBreak) {
                lineBreaks = true
            }
            if (!(c == '\n'.code || (c in 0x20..0x7E))) {
                if (c == 0x85 || (c in 0xA0..0xD7FF)
                    || (c in 0xE000..0xFFFD)
                    || (c in 0x10000..0x10FFFF)
                ) {
                    // unicode is used
                    //if (!this.allowUnicode) {
                    //    specialCharacters = true;
                    //}
                } else {
                    specialCharacters = true
                }
            }
            // Detect important whitespace combinations.
            if (c == ' '.code) {
                if (index == 0) {
                    leadingSpace = true
                }
                if (index == scalar.length - 1) {
                    trailingSpace = true
                }
                if (previousBreak) {
                    breakSpace = true
                }
                previousSpace = true
                previousBreak = false
            } else if (isLineBreak) {
                if (index == 0) {
                    leadingBreak = true
                }
                if (index == scalar.length - 1) {
                    trailingBreak = true
                }
                if (previousSpace) {
                    spaceBreak = true
                }
                previousSpace = false
                previousBreak = true
            } else {
                previousSpace = false
                previousBreak = false
            }

            // Prepare for the next character.
            index += c.charCount()
            preceededByWhitespace = Constant.NULL_BL_T.has(c) || isLineBreak
            followedByWhitespace = true
            if (index + 1 < scalar.length) {
                val nextIndex: Int =
                    index + scalar.codePointAt(index).charCount()
                if (nextIndex < scalar.length) {
                    followedByWhitespace = (Constant.NULL_BL_T.has(scalar.codePointAt(nextIndex))) || isLineBreak
                }
            }
        }
        // Let's decide what styles are allowed.
        var allowFlowPlain = true
        var allowBlockPlain = true
        var allowSingleQuoted = true
        var allowBlock = true
        // Leading and trailing whitespaces are bad for plain scalars.
        if (leadingSpace || leadingBreak || trailingSpace || trailingBreak) {
            allowBlockPlain = false
            allowFlowPlain = allowBlockPlain
        }
        // We do not permit trailing spaces for block scalars.
        if (trailingSpace) {
            allowBlock = false
        }
        // Spaces at the beginning of a new line are only acceptable for block
        // scalars.
        if (breakSpace) {
            allowSingleQuoted = false
            allowBlockPlain = allowSingleQuoted
            allowFlowPlain = allowBlockPlain
        }
        // Spaces followed by breaks, as well as special character are only
        // allowed for double quoted scalars.
        if (spaceBreak || specialCharacters) {
            allowBlock = false
            allowSingleQuoted = allowBlock
            allowBlockPlain = allowSingleQuoted
            allowFlowPlain = allowBlockPlain
        }
        // Although the plain scalar writer supports breaks, we never emit
        // multiline plain scalars in the flow context.
        if (lineBreaks) {
            allowFlowPlain = false
        }
        // Flow indicators are forbidden for flow plain scalars.
        if (flowIndicators) {
            allowFlowPlain = false
        }
        // Block indicators are forbidden for block plain scalars.
        if (blockIndicators) {
            allowBlockPlain = false
        }

        if (allowFlowPlain) return scalar

        return "\"" + scalar.escape(Escape.java) + '"'
    }

    internal class Constant private constructor(content: String) {
        private var content: String? = null
        var contains: BooleanArray = BooleanArray(128)
        var noASCII: Boolean = false

        init {
            contains.fill(false)
            //java.util.Arrays.fill(contains, false)
            val sb = StringBuilder()
            for (i in content.indices) {
                val c: Int = content.codePointAt(i)
                if (c < 128) contains[c] = true
                else sb.appendCodePoint(c)
            }
            if (sb.isNotEmpty()) {
                noASCII = true
                this.content = sb.toString()
            }
        }

        fun has(c: Int): Boolean {
            return if ((c < 128)) contains[c] else noASCII && content!!.indexOf(c.toChar(), 0) != -1
        }

        fun hasNo(c: Int): Boolean {
            return !has(c)
        }

        fun has(c: Int, additional: String): Boolean {
            return has(c) || additional.indexOf(c.toChar(), 0) != -1
        }

        fun hasNo(c: Int, additional: String): Boolean {
            return !has(c, additional)
        }

        companion object {
            private const val ALPHA_S = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_"

            private const val LINEBR_S = "\n\u0085\u2028\u2029"
            private const val FULL_LINEBR_S = "\r" + LINEBR_S
            private const val NULL_OR_LINEBR_S = "\u0000" + FULL_LINEBR_S
            private const val NULL_BL_LINEBR_S = " " + NULL_OR_LINEBR_S
            private const val NULL_BL_T_LINEBR_S = "\t" + NULL_BL_LINEBR_S
            private const val NULL_BL_T_S = "\u0000 \t"
            private const val URI_CHARS_S = ALPHA_S + "-;/?:@&=+$,_.!~*\'()[]%"

            val LINEBR: Constant = Constant(LINEBR_S)
            val FULL_LINEBR: Constant = Constant(FULL_LINEBR_S)
            val NULL_OR_LINEBR: Constant = Constant(NULL_OR_LINEBR_S)
            val NULL_BL_LINEBR: Constant = Constant(NULL_BL_LINEBR_S)
            val NULL_BL_T_LINEBR: Constant = Constant(NULL_BL_T_LINEBR_S)
            val NULL_BL_T: Constant = Constant(NULL_BL_T_S)
            val URI_CHARS: Constant = Constant(URI_CHARS_S)

            val ALPHA: Constant = Constant(ALPHA_S)
        }
    }
}

val Escape.Companion.yaml get() = YamlEscape